package thread;

import java.util.concurrent.PriorityBlockingQueue;

/**
 * 自行实现定时器 MyTime
 */

// 使用这个类来表示一个定时器的任务
class MyTask implements Comparable<MyTask> {
    // 要执行的任务
    private Runnable runnable;
    // 任务在什么时候执行（用毫秒时间戳表示）
    private long time;

    public MyTask(Runnable runnable, long time) {
        this.runnable = runnable;
        this.time = time;
    }

    // 获取任务的时间
    public long getTime() {
        return time;
    }

    // 执行任务
    public void run() {
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {
        // 返回 小于0，大于0, 0
        // this 比 o 小，返回 < 0
        // this 比 o 大，返回 > 0
        // this 和 o 相同，返回 0
        // 当前要实现的效果是，队首元素时间最小的任务
        // this.time 和 o.time 谁减谁不要记，试一下（运行代码）就知道了
        return (int) (this.time - o.time);
    }
}

// 自己写的一个定时器
class MyTime {
    // 扫描线程
    private Thread t = null;

    // 有一个阻塞优先级队列，来保存任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();
    // 专门使用这个对象来加锁/等待通知
    private Object loker = new Object();

    public MyTime() {
        t = new Thread(() -> {
            while (true) {
                try {
                    /*
                        1、取出队首元素，检查看队首元素任务是否到时间了
                        2、如果时间没到，就把任务塞回队列中去
                        3、如果时间到了，就执行任务
                     */
                    synchronized (loker) {
                        // 此处需要把锁的范围扩大，就可以保证，执行 notify 的时候，wait 确实是已经执行完了
                        // 就可以预防出现 notify 的时候还没有准备好 wait 的情况了
                        MyTask myTask = queue.take();
                        long curTime = System.currentTimeMillis();
                        if(curTime < myTask.getTime()) {
                            // 还没到点，先不必执行，比如现在是13:00，任务是14:00执行
                            queue.put(myTask);

                            // 在 put 之后，进行一个 wait，伴随加锁操作
                            /*
                                带超时时间的 wait 就可以保证：
                                1、当新任务来了，就随时 notify 唤醒
                                2、如果没有新任务，就最多等到之前旧任务中的最早任务时间到，就 notify 被唤醒
                             */
                            loker.wait(myTask.getTime() - curTime);
                        }else {
                            // 时间到了，执行任务
                            myTask.run();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }

    /**
     * 指定两个参数
     * @param runnable 任务的内容
     * @param after    指任务在多少毫秒之后执行，形如 1000
     */
    public void schedule(Runnable runnable,long after) {
        // 注意这里的时间上的换算
        MyTask task = new MyTask(runnable,System.currentTimeMillis() + after);
        queue.put(task);
        synchronized (loker) {
            loker.notify();
        }
    }
}

public class ThreadDemo25 {
    public static void main(String[] args) {
        MyTime myTime = new MyTime();
        myTime.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务1");
            }
        },1000);
        myTime.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2");
            }
        },2000);
    }
}
